import unittest
from ply import lex

RESERVED = {
    "and": "AND",
    "or": "OR",
    "div": "DIV",
    "eq": "EQ",
    "ne": "NE",
    "le": "LE",
    "lt": "LT",
    "ge": "GE",
    "gt": "GT",
    "mod": "MOD",
    "not": "NOT",
    "between": "BETWEEN",
    "null": "NULL",
    "true": "TRUE",
    "false": "FALSE",
    "matches": "REG_MATCH",
    "instanceof": "INST_OF"
}

tokens = ('ID', 'INTEGER', 'FLOATING', 'STR', 'COMMA', 'COLON', 'ASSIGN',
          'EQ', 'NE', 'LE', 'LT', 'GE', 'GT', 'BETWEEN',
          'QMARK',
          'AND', 'OR',
          'PLUS', 'MINUS', 'MULT', 'DIV', 'MOD', 'POWER',
          'NOT', 'INC', 'DEC',
          'LPAR', 'RPAR', 'LSQUARE', 'RSQUARE', 'LCURLY', 'RCURLY',
          'DOT', 'HASH', 'SAFE_NAVI', 'ELVIS',
          'NULL', 'TRUE', 'FALSE',
          'REG_MATCH', 'BEAN_REF',
          'TYPE_REF', 'INST_OF',
          'PROJECT', 'SELECT')


def t_FLOATING(t):
    r'\d+\.\d+'
    t.value = float(t.value)
    return t


def t_INTEGER(t):
    r'\d+'
    t.value = int(t.value)
    return t


def t_STR(t):
    r"'([^'\n]|'')*'"
    t.value = t.value[1:-1].replace("''", "'")
    return t


def t_ID(t):
    r'[a-zA-Z_][a-zA-Z0-9_]*'
    if t.value == 'T':
        t.type = 'TYPE_REF'
    else:
        t.type = RESERVED.get(t.value.lower(), "ID")
    return t


t_QMARK = r'\?'
t_ASSIGN = r'='
t_COMMA = r','
t_COLON = r':'
t_NOT = r'!'
t_EQ = r'=='
t_NE = r'!='
t_LE = r'<='
t_LT = r'<'
t_GE = r'>='
t_GT = r'>'
t_PLUS = r'\+'
t_MINUS = r'-'
t_MULT = r'\*'
t_DIV = r'/'
t_MOD = r'%'
t_POWER = r'\^'
t_AND = r'&&'
t_OR = r'\|\|'
t_LSQUARE = r'\['
t_RSQUARE = r'\]'
t_LCURLY = r'{'
t_RCURLY = r'}'
t_LPAR = r'\('
t_RPAR = r'\)'
t_DOT = r'\.'
t_SAFE_NAVI = r'\?\.'
t_ELVIS = r'\?:'
t_HASH = r'\#'
t_INC = r'\+\+'
t_DEC = r'--'
t_BEAN_REF = r'@'
t_PROJECT = r'!\['
t_SELECT = r'\?\['
t_ignore = ' \t'


def t_newline(t):
    r'\n+'
    t.lexer.lineno += t.value.count('\n')


def t_error(t):
    t.lexer.skip(1)


precedence = (
    ('right', 'ASSIGN'),
    ('left', 'ELVIS', 'QMARK', 'COLON'),
    ('left', 'OR'),
    ('left', 'AND'),
    ('left', 'EQ', 'NE', 'LE', 'LT', 'GE', 'GT', 'REG_MATCH', 'INST_OF'),
    ('left', 'PLUS', 'MINUS'),
    ('left', 'MULT', 'DIV', 'MOD'),
    ('left', 'INC', 'DEC'),
    ('right', 'POWER'),
    ('right', 'NOT', 'UMINUS', 'UINC', 'UDEC'),
    ('left', 'DOT', 'SAFE_NAVI')
)


class SpelLexerTest(unittest.TestCase):
    def parse_token(self, s):
        lexer = lex.lex()
        lexer.input(s)

        tks = []
        while True:
            token = lex.token()
            if not token:
                break
            tks.append(token)
        return [(t.value, t.type) for t in tks]

    def test_lex_normal(self):
        self.assertEqual(self.parse_token("#a.b*4<5"), [('#', 'HASH'), ('a', 'ID'), ('.', 'DOT'),
                                                        ('b', 'ID'), ('*', 'MULT'), (4, 'INTEGER'),
                                                        ('<', 'LT'), (5, 'INTEGER')])

        self.assertEqual(self.parse_token("#a.b*4 <= 5"), [('#', 'HASH'), ('a', 'ID'), ('.', 'DOT'),
                                                           ('b', 'ID'), ('*', 'MULT'), (4, 'INTEGER'),
                                                           ('<=', 'LE'), (5, 'INTEGER')])

        self.assertEqual(self.parse_token("4/10.0"), [(4, 'INTEGER'),
                                                      ('/', 'DIV'), (10.0, 'FLOATING')])

        self.assertEqual(self.parse_token("4 div 10.0"), [(4, 'INTEGER'),
                                                          ('div', 'DIV'), (10.0, 'FLOATING')])

        self.assertEqual(self.parse_token("++#a"), [('++', 'INC'),
                                                    ('#', 'HASH'), ('a', 'ID')])

        self.assertEqual(self.parse_token("#l.![#this+1]"), [("#", 'HASH'),
                                                             ('l', 'ID'), ('.', 'DOT'),
                                                             ('![', 'PROJECT'), ('#', 'HASH'),
                                                             ('this', 'ID'), ('+', 'PLUS'),
                                                             (1, 'INTEGER'), (']', 'RSQUARE')])

    def test_lex_str(self):
        self.assertEqual(self.parse_token("'abc'"), [('abc', 'STR')])
        self.assertEqual(self.parse_token("'ab''c'"), [("ab'c", 'STR')])
        self.assertEqual(self.parse_token("'abc'=='bcd'"), [("abc", 'STR'), ('==', 'EQ'), ('bcd', 'STR')])
        self.assertEqual(self.parse_token("'ab''c'=='b''cd'"), [("ab'c", 'STR'), ('==', 'EQ'), ("b'cd", "STR")])
        self.assertEqual(self.parse_token("'ab''c'=='bcd'"), [("ab'c", 'STR'), ('==', 'EQ'), ("bcd", "STR")])


if __name__ == '__main__':
    unittest.main()
